package game;

import javax.swing.JOptionPane;

import ai.*;
import gui.Gui;

public class Game {

	public static final byte EMPTY = 0;
	public static final byte PLAYER1 = 1;
	public static final byte PLAYER2 = 2;

	public static final byte PLAYER1_WIN = 0;
	public static final byte DRAW = 1;
	public static final byte UNCLEAR = 2;
	public static final byte PLAYER2_WIN = 3;

	public static final int XSIZE = 7;
	public static final int YSIZE = 6;
	public static final int FIELDWIDTH = 80;
	
	//Ai variables
	public static final byte AI_HUMAN = 0;
	public static final byte AI_RANDOM = 1;
	public static final byte AI_MINMAX = 2;
	public static final byte AI_ALPHABETA = 3;
	public static final byte AI_MINMAXIMPROVED = 4;
	public static final byte AI_ALPHABETAIMPROVED = 5;
	public static final byte AI_MINMAX2 = 6;
	//public static final byte AI_ALPHABETATABLE = 7;
	public static final byte AI_ALPHABETAMULTITHREADED = 8;
	
	
	private Gui gui;
	private byte[][] playingField;
	private byte currentside;
	
	private boolean player1isHuman;
	private boolean player2isHuman;
	
	private Ai aiPlayer1;
	private Ai aiPlayer2;
	private Thread aiThread1;
	private Thread aiThread2;
	


	//The main
	public static void main(String[] args)
	{
		new Game();
	}

	//Contructor
	public Game()
	{
		//Initiate the playing field.
		this.playingField = new byte[XSIZE][YSIZE];
		this.newGame();
		
		this.aiPlayer1 = null;
		this.aiPlayer2 = null;

		this.aiThread1 = null;
		this.aiThread2 = null;
		
		this.player1isHuman = false;
		this.player2isHuman = false;
		
		//Load the gui
		this.gui = new Gui(this);
	}
	
	//Start the game
	public synchronized void start(int player1Value, int player2Value)
	{
		//Shutdown down the previous Ai
		shutdownAi();
		
		//Reset the playing field
		this.newGame();
		
		//Player 1 always starts
		this.currentside = Game.PLAYER1;
		
		this.player1isHuman = (player1Value == AI_HUMAN);
		this.player2isHuman = (player2Value == AI_HUMAN);
		
		//Set the ai for player 1, if needed
		if (!this.player1isHuman)
		{
			this.aiPlayer1 = getAi(player1Value, PLAYER1);
			this.aiThread1 = new Thread(this.aiPlayer1, "Ai Player 1");
			this.aiThread1.start();
		}
		
		//Set the ai for player 2, if needed
		if (!this.player2isHuman)
		{
			this.aiPlayer2 = getAi(player2Value, PLAYER2);
			this.aiThread2 = new Thread(this.aiPlayer2, "Ai Player 2");
			this.aiThread2.start();
		}
		
		//Notify the Ai Thread
		notifyAll();
		
		
		gui.repaint();
	}
	
	
	//Play the game
	public void playGame(int column)
	{
		synchronized(this)
		{
			/* DEBUG: test hashcode rommel
			Position pos = new Position(this.getPlayingField());
			
			Position.printTheBits(pos.hashCodeLong());
			*/
			
			if (!gameHasEnded())
			{
				//If an invalid column was selected
				if (! placeToken(column, this.getCurrentSide()))
				{
					JOptionPane.showMessageDialog(null, "Invalid column selected.");
					return;
				}

				gui.repaint();

				
				//Notify the Ai thread
				if (gameHasEnded())
				{
					shutdownAi();
				}
				else
				{
					switchCurrentSide();
				}
				
				notifyAll();
			}
		}

		if (gameHasEnded())
		{
			//Message boxes can't exist within synchronized blocks or something?
			//http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4128106
			displayGameStatus();
		}
	}
	
	
	//Create the Ai engine
	private synchronized Ai getAi(int aiValue, byte playerValue)
	{
		System.out.println("Setting ai: "+aiValue+" for player: "+playerValue);
		
		switch(aiValue)
		{
			case AI_RANDOM:
				return new Randomiser(this, playerValue);
			case AI_MINMAX:
				return new Minmax(this, playerValue);
			case AI_ALPHABETA:
				return new AlphaBeta(this, playerValue);
			case AI_MINMAXIMPROVED:
				return new MinmaxImproved(this, playerValue);
			case AI_ALPHABETAIMPROVED:
				return new AlphaBetaImproved(this, playerValue);
			case AI_MINMAX2:
				return new MinMax2(this, playerValue);
			//case AI_ALPHABETATABLE:
			//	return new AlphaBetaTranspositionTable(this, playerValue);
			case AI_ALPHABETAMULTITHREADED:
				return new AlphaBetaMultiThreaded(this, playerValue);
		}
		
		return null;
	}
	
	
	private void shutdownAi()
	{
		if (this.aiPlayer1 != null)
		{
			this.aiPlayer1.shutdown();
			this.aiPlayer1 = null;
			this.aiThread1 = null;
		}
		
		if (this.aiPlayer2 != null)
		{
			this.aiPlayer2.shutdown();
			this.aiPlayer2 = null;
			this.aiThread2 = null;
		}

		notifyAll();
	}
	
	
	//Set the playingfield to zero
	private synchronized void newGame()
	{
		//System.out.println();
		//System.out.println("DEBUG: Newgame.");
		
		for(int i = 0; i < XSIZE; i++)
		{
			for(int j = 0; j < YSIZE; j++)
			{
				this.playingField[i][j] = Game.EMPTY;
			}
		}
	}
	

	//Display the end message
	private void displayGameStatus()
	{
		if (checkFourOnRow(this.playingField) == Game.PLAYER1_WIN)
		{
			JOptionPane.showMessageDialog(null, "Player 1 has won.");
		}
		else if (checkFourOnRow(this.playingField) == Game.DRAW)
		{
			JOptionPane.showMessageDialog(null, "Draw!");
		}
		else if (checkFourOnRow(this.playingField) == Game.PLAYER2_WIN)
		{
			JOptionPane.showMessageDialog(null, "Player 2 has won.");
		}
	}

	//	Place a token in a column
	private synchronized boolean placeToken(int column, byte side)
	{
		//System.out.println("DEBUG: Placing token column: "+column+" Side: "+side);
		
		for (int j = Game.YSIZE - 1; j >= 0; j--)
		{
			if (this.playingField[column][j] == Game.EMPTY)
			{
				this.playingField[column][j] = side;
				this.gui.repaint();
				return true;
			}
		}
		return false;
	}
	
	//Return the playing field array
	public synchronized byte[][] getPlayingField()
	{
		return this.playingField;
	}
	
	public synchronized byte getCurrentSide()
	{
		return this.currentside;
	}
	
	public synchronized boolean currentsideIsHuman()
	{
		if (this.currentside == Game.PLAYER1)
		{
			return this.player1isHuman;
		}
		else
		{
			return this.player2isHuman;
		}
	}
	
	private synchronized void switchCurrentSide()
	{
		if (this.currentside == Game.PLAYER1)
		{
			this.currentside = Game.PLAYER2;
		}
		else
		{
			this.currentside = Game.PLAYER1;
		}
	}
	
	
	public synchronized boolean gameHasEnded()
	{
		return checkFourOnRow(this.playingField) != Game.UNCLEAR;
	}
	
	
	//Check for four in a row
	public synchronized int checkFourOnRow(byte[][] grid)
	{
		int fourOnRowCounter, side, sideWin, x, y;

		boolean draw = true;

		//Check every location on the playingfield
		for(int i = 0; i < Game.XSIZE; i++)
		{
			for(int j = Game.YSIZE - 1; j >= 0; j--)
			{
				//If the locatie is empty go to the next column
				if (grid[i][j] == Game.EMPTY)
				{
					//We've located an empty field so it's not draw
					draw = false;

					//Skip to the next column
					break;
				}
				else
				{
					side = grid[i][j];

					//Determine the returnvalue if we find a four-on-row
					if (side == PLAYER1)
					{
						sideWin = PLAYER1_WIN;
					}
					else
					{
						sideWin = PLAYER2_WIN;
					}


					//Set the counter to 1 because we've already found one-on-row
					fourOnRowCounter = 1;
					//Check in front of the current location
					for (x = i+1; x < XSIZE; x++)
					{
						if (side == grid[x][j])
						{
							fourOnRowCounter++;
						}
						else
						{
							break;
						}
					}
					//Front - check
					if (fourOnRowCounter >= 4)
					{
						return sideWin;
					}


					//Reset the counter to 1
					fourOnRowCounter = 1;
					//Check to the upper right of the current location
					for (x = i + 1, y = j-1; x < XSIZE && y >= 0; x++, y--)
					{
						if (side == grid[x][y])
						{
							fourOnRowCounter++;
						}
						else
						{
							break;
						}
					}
					//Diagonal - check
					if (fourOnRowCounter >= 4)
					{
						return sideWin;
					}


					//Reset the counter to 1
					fourOnRowCounter = 1;
					//Check above the current location
					for (y = j-1; y >= 0; y--)
					{
						if (side == grid[i][y])
						{
							fourOnRowCounter++;
						}
						else
						{
							break;
						}
					}
					//Vertical - check
					if (fourOnRowCounter >= 4)
					{
						return sideWin;
					}



					//Reset the counter to 1
					fourOnRowCounter = 1;
					//Diagonal - up left
					for (x = i - 1, y = j-1; x >= 0 && y >= 0; x--, y--)
					{
						if (side == grid[x][y])
						{
							fourOnRowCounter++;
						}
						else
						{
							break;
						}
					}
					//Diagonal - check
					if (fourOnRowCounter >= 4)
					{
						return sideWin;
					}

				}
			}
		}

		if (draw)
		{
			return DRAW;
		}

		return UNCLEAR;
	}



}
